"""
免责声明：
本脚本仅供学习交流使用，请勿用于商业用途或非法目的。
使用者应对自己的行为负责，脚本作者不承担任何法律责任。
使用前请确保遵守中国电信相关服务条款，合理使用脚本功能。

使用说明：
1. 环境变量配置：
   - chinaTelecomAccount: 手机号#密码#WxPusher的UID (多个账号用@或&分隔)
   - MEXZ: 兑换策略配置，格式："0.5,5;1,10" (上午;下午)
   - WXPUSHER_APP_TOKEN: 微信推送token
   - WXPUSHER_UID: 微信推送UID
   - OUTER_LOOP_COUNT: 外层循环次数，默认20
   - INNER_LOOP_COUNT: 内层循环次数，默认10

2. 运行时间：
   - 上午场: 10:00:00
   - 下午场: 14:00:00

3. 功能特点：
   - 多账号支持
   - 自动登录验证
   - 智能时间同步
   - 微信消息推送
   - 失败重试机制
   - 详细的日志输出

脚本内容：
本脚本通过模拟中国电信APP的登录和兑换流程，实现金豆自动兑换话费功能。
主要包含以下模块：
- 时间同步：获取网络时间确保准确性
- 登录认证：处理用户登录和token获取
- 兑换逻辑：多线程并发兑换请求
- 消息推送：通过微信推送兑换结果
- 日志记录：详细记录操作过程和结果

使用方法：
1. 配置所需的环境变量
2. 安装依赖：pip install requests aiohttp pycryptodome
3. 设置定时任务或手动运行
"""

"""
cron: 0 59 9,13 * * *
new Env('电信金豆兑换话费');
"""
import subprocess
import sys
import asyncio
import aiohttp
import os
import execjs
import requests
import re
import time as time_module  # 重命名导入以避免冲突
import json
import random
import datetime
import base64
import ssl
import certifi
import traceback
from Crypto.PublicKey import RSA
from Crypto.Cipher import PKCS1_v1_5
from Crypto.Cipher import DES3
from Crypto.Util.Padding import pad, unpad
from Crypto.Cipher import AES
from http import cookiejar
from requests.adapters import HTTPAdapter
from requests.packages.urllib3.util.ssl_ import create_urllib3_context


def get_network_time():
    """从淘宝接口获取网络时间"""
    url = "https://acs.m.taobao.com/gw/mtop.common.getTimestamp/"
    try:
        response = requests.get(url)
        if response.status_code == 200:
            data = response.json()
            if "data" in data and "t" in data["data"]:
                timestamp = int(data["data"]["t"])
                return datetime.datetime.fromtimestamp(timestamp / 1000)
            else:
                raise ValueError("接口返回数据格式错误，未找到时间戳")
        else:
            raise Exception(f"获取网络时间失败，状态码: {response.status_code}")
    except Exception as e:
        current_time = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f')[:-3]
        print(f"[{current_time}] [错误] 获取网络时间失败: {str(e)}")
        return datetime.datetime.now()


# 获取本地时间和网络时间
local_time = datetime.datetime.now()
network_time = get_network_time()

# 计算时间差
time_diff = network_time - local_time

# 输出时间差，精确到微秒
current_time = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f')[:-3]
print(f"[{current_time}] [系统信息] 本地时间: {local_time.strftime('%Y-%m-%d %H:%M:%S.%f')[:-3]}")
print(f"[{current_time}] [系统信息] 网络时间: {network_time.strftime('%Y-%m-%d %H:%M:%S.%f')[:-3]}")
print(f"[{current_time}] [系统信息] 时间差: {time_diff.total_seconds():.6f} 秒")

# 默认兑换策略
MEXZ = os.getenv("MEXZ")

# 定义时间段
morning_start = datetime.time(9, 30, 3)
morning_end = datetime.time(10, 10, 30)
afternoon_start = datetime.time(13, 30, 3)
afternoon_end = datetime.time(14, 10, 30)

# 获取当前时间
now = get_network_time().time()

# 判断当前时间是否在指定的时间段内
if (morning_start <= now <= morning_end) or (afternoon_start <= now <= afternoon_end):
    if not MEXZ:
        MEXZ = "0.5,5,6;1,10,3"
else:
    MEXZ = "0.5,5,6;1,10,3"

# 解析 MEXZ 配置
morning_exchanges, afternoon_exchanges = MEXZ.split(';')
morning_exchanges = [f"{x}元话费" for x in morning_exchanges.split(',')]
afternoon_exchanges = [f"{x}元话费" for x in afternoon_exchanges.split(',')]

current_time = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f')[:-3]
print(f"[{current_time}] [配置信息] 上午兑换列表: {morning_exchanges}")
print(f"[{current_time}] [配置信息] 下午兑换列表: {afternoon_exchanges}")


# 从环境变量中获取代理池地址
DY_PROXY = os.getenv("DY_PROXY123")
current_time = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f')[:-3]
print(f"[{current_time}] [代理信息] 代理池地址: {'已配置' if DY_PROXY else '未配置'}")

# 新增：从环境变量获取外层循环次数，默认20次
OUTER_LOOP_COUNT = int(os.getenv("OUTER_LOOP_COUNT", "10"))
INNER_LOOP_COUNT = int(os.getenv("INNER_LOOP_COUNT", "5"))

current_time = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f')[:-3]
print(f"[{current_time}] [配置信息] 外层循环次数: {OUTER_LOOP_COUNT}")
print(f"[{current_time}] [配置信息] 内层循环次数: {INNER_LOOP_COUNT}")


async def get_proxy_from_pool():
    """从代理池获取代理IP"""
    current_time = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f')[:-3]
    if not DY_PROXY:
        raise ValueError("DY_PROXY 环境变量未设置")

    try:
        async with aiohttp.ClientSession() as session:
            start_time = time_module.time()
            async with session.get(DY_PROXY) as response:
                end_time = time_module.time()
                if response.status != 200:
                    raise Exception(f"从代理池获取代理IP失败，状态码: {response.status}")
                proxy_ip = await response.text()
                proxy_ip = proxy_ip.strip()
                print(f"[{current_time}] [代理信息] 成功获取代理IP，耗时: {end_time - start_time:.3f}秒，IP: {proxy_ip}")
                return proxy_ip
    except Exception as e:
        print(f"[{current_time}] [代理错误] 获取代理IP失败: {str(e)}")
        raise


class BlockAll(cookiejar.CookiePolicy):
    return_ok = set_ok = domain_return_ok = path_return_ok = lambda self, *args, **kwargs: False
    netscape = True
    rfc2965 = hide_cookie2 = False


def print_time_log(m):
    current_time = get_network_time().strftime('%Y-%m-%d %H:%M:%S.%f')[:-3]
    print(f"[{current_time}] {m}")


ORIGIN_CIPHERS = ('DEFAULT@SECLEVEL=1')


class DESAdapter(HTTPAdapter):
    def __init__(self, *args, **kwargs):
        CIPHERS = ORIGIN_CIPHERS.split(':')
        random.shuffle(CIPHERS)
        CIPHERS = ':'.join(CIPHERS)
        self.CIPHERS = CIPHERS + ':!aNULL:!eNULL:!MD5'
        super().__init__(*args, **kwargs)

    def init_poolmanager(self, *args, **kwargs):
        context = create_urllib3_context(ciphers=self.CIPHERS)
        context.check_hostname = False
        kwargs['ssl_context'] = context
        return super(DESAdapter, self).init_poolmanager(*args, **kwargs)

    def proxy_manager_for(self, *args, **kwargs):
        context = create_urllib3_context(ciphers=self.CIPHERS)
        context.check_hostname = False
        kwargs['ssl_context'] = context
        return super(DESAdapter, self).proxy_manager_for(*args, **kwargs)


requests.packages.urllib3.disable_warnings()
ssl_context = ssl.create_default_context()
ssl_context.set_ciphers("DEFAULT@SECLEVEL=1")
ss = requests.session()
ss.verify = certifi.where()
ss.headers = {
    "User-Agent": "Mozilla/5.0 (Linux; Android 13; 22081212C Build/TKQ1.220829.002) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.5112.97 Mobile Safari/537.36",
    "Referer": "https://wapact.189.cn:9001/JinDouMall/JinDouMall_independentDetails.html"
}
ss.mount('https://', DESAdapter())
ss.cookies.set_policy(BlockAll())

# 全局变量初始化
yc = 1
wt = 0
kswt = 0.1
yf = get_network_time().strftime("%Y%m")
ip_list = []
jp = {"9": {}, "13": {}}

# 加载兑换记录
try:
    with open('电信金豆换话费.log') as fr:
        dhjl = json.load(fr)
    current_time = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f')[:-3]
    print(f"[{current_time}] [数据加载] 成功加载兑换记录文件")
except Exception as e:
    current_time = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f')[:-3]
    print(f"[{current_time}] [数据加载] 首次运行或记录文件不存在，创建新记录: {str(e)}")
    dhjl = {}

if yf not in dhjl:
    dhjl[yf] = {}
else:
    # 将现有字符串记录转换为集合（仅首次加载时执行）
    for di in dhjl[yf]:
        if isinstance(dhjl[yf][di], str):
            # 拆分字符串为列表，去重后转为集合
            phone_list = dhjl[yf][di].strip('#').split('#') if dhjl[yf][di] else []
            dhjl[yf][di] = set(phone_list)

# 加载token缓存
load_token_file = 'chinaTelecom_cache.json'
try:
    with open(load_token_file, 'r') as f:
        load_token = json.load(f)
    current_time = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f')[:-3]
    print(f"[{current_time}] [数据加载] 成功加载token缓存，共{len(load_token)}条记录")
except Exception as e:
    current_time = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f')[:-3]
    print(f"[{current_time}] [数据加载] token缓存文件不存在，创建新缓存: {str(e)}")
    load_token = {}

# 错误代码映射
errcode = {
    "0": "兑换成功✨",
    "412": "兑换次数已达上限💔",
    "413": "商品已兑完💨",
    "420": "未知错误😥",
    "410": "该活动未开始⏳",
    "501": "服务器处理错误💻",
    "Y0001": "当前等级不足，去升级兑当前话费📈",
    "Y0002": "使用翼相连网络600分钟可兑换此奖品📶",
    "Y0003": "共享流量400M可兑换此奖品💧",
    "Y0004": "共享流量2GB可兑换此奖品💧",
    "Y0005": "当前等级不足，去升级兑当前话费📈",
    "E0001": "您的网龄不足10年，暂不能兑换⏳"
}

# 加密相关配置
key = b'1234567`90koiuyhgtfrdews'
iv = 8 * b'\0'

public_key_b64 = '''-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDBkLT15ThVgz6/NOl6s8GNPofdWzWbCkWnkaAm7O2LjkM1H7dMvzkiqdxU02jamGRHLX/ZNMCXHnPcW/sDhiFCBN18qFvy8g6VYb9QtroI09e176s+ZCtiv7hbin2cCTj99iUpnEloZm19lwHyo69u5UMiPMpq0/XKBO8lYhN/gwIDAQAB
-----END PUBLIC KEY-----'''

public_key_data = '''-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC+ugG5A8cZ3FqUKDwM57GM4io6JGcStivT8UdGt67PEOihLZTw3P7371+N47PrmsCpnTRzbTgcupKtUv8ImZalYk65dU8rjC/ridwhw9ffW2LBwvkEnDkkKKRi2liWIItDftJVBiWOh17o6gfbPoNrWORcAdcbpk2L+udld5kZNwIDAQAB
-----END PUBLIC KEY-----'''


def t(h):
    """计算目标时间戳"""
    date = get_network_time()
    date_zero = date.replace(hour=h, minute=59, second=20)
    date_zero_time = time_module.mktime(date_zero.timetuple())
    current_time = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f')[:-3]
    print(f"[{current_time}] [时间计算] 计算小时{h}的目标时间戳: {date_zero_time}")
    return date_zero_time


def encrypt(text):
    """DES3加密"""
    try:
        cipher = DES3.new(key, DES3.MODE_CBC, iv)
        ciphertext = cipher.encrypt(pad(text.encode(), DES3.block_size))
        return ciphertext.hex()
    except Exception as e:
        current_time = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f')[:-3]
        print(f"[{current_time}] [加密错误] DES3加密失败: {str(e)}")
        raise


def decrypt(text):
    """DES3解密"""
    try:
        ciphertext = bytes.fromhex(text)
        cipher = DES3.new(key, DES3.MODE_CBC, iv)
        plaintext = unpad(cipher.decrypt(ciphertext), DES3.block_size)
        return plaintext.decode()
    except Exception as e:
        current_time = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f')[:-3]
        print(f"[{current_time}] [解密错误] DES3解密失败: {str(e)}")
        raise


def b64(plaintext):
    """RSA加密并Base64编码"""
    try:
        public_key = RSA.import_key(public_key_b64)
        cipher = PKCS1_v1_5.new(public_key)
        ciphertext = cipher.encrypt(plaintext.encode())
        return base64.b64encode(ciphertext).decode()
    except Exception as e:
        current_time = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f')[:-3]
        print(f"[{current_time}] [加密错误] RSA加密失败: {str(e)}")
        raise


def encrypt_para(plaintext):
    """参数加密"""
    try:
        public_key = RSA.import_key(public_key_data)
        cipher = PKCS1_v1_5.new(public_key)
        ciphertext = cipher.encrypt(plaintext.encode())
        return ciphertext.hex()
    except Exception as e:
        current_time = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f')[:-3]
        print(f"[{current_time}] [加密错误] 参数加密失败: {str(e)}")
        raise


def encode_phone(text):
    """手机号编码"""
    encoded_chars = []
    for char in text:
        encoded_chars.append(chr(ord(char) + 2))
    return ''.join(encoded_chars)


def ophone(t):
    """手机号AES加密"""
    try:
        key = b'34d7cb0bcdf07523'
        utf8_t = t.encode('utf-8')
        cipher = AES.new(key, AES.MODE_ECB)
        ciphertext = cipher.encrypt(pad(utf8_t, AES.block_size))
        return ciphertext.hex()
    except Exception as e:
        current_time = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f')[:-3]
        print(f"[{current_time}] [加密错误] 手机号AES加密失败: {str(e)}")
        raise


def send(uid, content):
    """发送微信推送"""
    current_time = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f')[:-3]
    appToken = os.getenv("WXPUSHER_APP_TOKEN")
    uid = os.getenv("WXPUSHER_UID")

    if not appToken or not uid:
        print(f"[{current_time}] [推送错误] WXPUSHER_APP_TOKEN 或 WXPUSHER_UID 未设置，无法发送推送")
        return None

    try:
        start_time = time_module.time()
        r = requests.post(
            'https://wxpusher.zjiecode.com/api/send/message',
            json={"appToken": appToken, "content": content, "contentType": 1, "uids": [uid]}
        ).json()
        end_time = time_module.time()
        print(f"[{current_time}] [推送信息] 推送请求已发送，耗时: {end_time - start_time:.3f}秒，响应: {r}")
        return r
    except Exception as e:
        print(f"[{current_time}] [推送错误] 发送推送失败: {str(e)}")
        return None


def userLoginNormal(phone, password):
    """正常登录获取ticket"""
    current_time = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f')[:-3]
    masked_phone = phone[:3] + '****' + phone[-4:]
    print(f"[{current_time}] [登录操作] 开始登录账号: {masked_phone}")
    
    try:
        # 生成UUID
        alphabet = 'abcdef0123456789'
        uuid = [
            ''.join(random.sample(alphabet, 8)), 
            ''.join(random.sample(alphabet, 4)),
            '4' + ''.join(random.sample(alphabet, 3)), 
            ''.join(random.sample(alphabet, 4)),
            ''.join(random.sample(alphabet, 12))
        ]
        timestamp = get_network_time().strftime("%Y%m%d%H%M%S")
        loginAuthCipherAsymmertric = 'iPhone 14 15.4.' + uuid[0] + uuid[1] + phone + timestamp + password[:6] + '0$$$0.'

        # 发送登录请求
        start_time = time_module.time()
        r = ss.post(
            'https://appgologin.189.cn:9031/login/client/userLoginNormal',
            json={
                "headerInfos": {
                    "code": "userLoginNormal", 
                    "timestamp": timestamp,
                    "broadAccount": "", 
                    "broadToken": "",
                    "clientType": "#10.5.0#channel50#iPhone 14 Pro Max#",
                    "shopId": "20002", 
                    "source": "110003",
                    "sourcePassword": "Sid98s", 
                    "token": "",
                    "userLoginName": encode_phone(phone)
                },
                "content": {
                    "attach": "test", 
                    "fieldData": {
                        "loginType": "4",
                        "accountType": "",
                        "loginAuthCipherAsymmertric": b64(loginAuthCipherAsymmertric),
                        "deviceUid": uuid[0] + uuid[1] + uuid[2],
                        "phoneNum": encode_phone(phone),
                        "isChinatelecom": "0",
                        "systemVersion": "15.4.0",
                        "authentication": encode_phone(password)
                    }
                }
            }
        ).json()
        end_time = time_module.time()
        print(f"[{current_time}] [登录操作] 登录请求完成，耗时: {end_time - start_time:.3f}秒")

        # 处理登录响应
        if not r:
            print(f"[{current_time}] [登录错误] {masked_phone} 登录请求返回空数据")
            return False

        if 'responseData' not in r or not r['responseData']:
            print(f"[{current_time}] [登录错误] {masked_phone} 响应数据格式错误: {r}")
            return False

        if 'data' not in r['responseData'] or not r['responseData']['data']:
            print(f"[{current_time}] [登录错误] {masked_phone} 数据字段缺失: {r}")
            return False

        if 'loginSuccessResult' not in r['responseData']['data']:
            print(f"[{current_time}] [登录错误] {masked_phone} 登录结果缺失: {r}")
            return False

        login_result = r['responseData']['data']['loginSuccessResult']
        if login_result:
            load_token[phone] = login_result
            with open(load_token_file, 'w') as f:
                json.dump(load_token, f)
            print(f"[{current_time}] [登录成功] {masked_phone} 登录成功，已缓存token")
            ticket = get_ticket(phone, login_result['userId'], login_result['token'])
            return ticket

        print(f"[{current_time}] [登录失败] {masked_phone} 登录结果为空: {r}")
        return False
        
    except Exception as e:
        print(f"[{current_time}] [登录错误] {masked_phone} 登录过程出错: {str(e)}")
        return False


def get_ticket(phone, userId, token):
    """获取ticket"""
    current_time = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f')[:-3]
    masked_phone = phone[:3] + '****' + phone[-4:]
    print(f"[{current_time}] [Ticket获取] 开始获取 {masked_phone} 的ticket")
    
    try:
        start_time = time_module.time()
        r = ss.post(
            'https://appgologin.189.cn:9031/map/clientXML',
            data='<Request><HeaderInfos><Code>getSingle</Code><Timestamp>' + get_network_time().strftime("%Y%m%d%H%M%S") +
            '</Timestamp><BroadAccount></BroadAccount><BroadToken></BroadToken><ClientType>#9.6.1#channel50#iPhone 14 Pro Max#</ClientType>' +
            '<ShopId>20002</ShopId><Source>110003</Source><SourcePassword>Sid98s</SourcePassword><Token>' + token +
            '</Token><UserLoginName>' + phone + '</UserLoginName></HeaderInfos><Content><Attach>test</Attach>' +
            '<FieldData><TargetId>' + encrypt(userId) + '</TargetId><Url>4a6862274835b451</Url></FieldData></Content></Request>',
            headers={'user-agent': 'CtClient;10.4.1;Android;13;22081212C;NTQzNzgx!#!MTgwNTg1'}
        )
        end_time = time_module.time()
        print(f"[{current_time}] [Ticket获取] 请求完成，耗时: {end_time - start_time:.3f}秒，状态码: {r.status_code}")

        tk = re.findall('<Ticket>(.*?)</Ticket>', r.text)
        if len(tk) == 0:
            print(f"[{current_time}] [Ticket错误] {masked_phone} 未找到ticket，响应内容: {r.text[:200]}")
            return False
        
        ticket = decrypt(tk[0])
        print(f"[{current_time}] [Ticket成功] {masked_phone} 获取ticket成功")
        return ticket
        
    except Exception as e:
        print(f"[{current_time}] [Ticket错误] {masked_phone} 获取ticket失败: {str(e)}")
        return False


async def exchange(phone, s, title, aid, uid, amount):
    """执行兑换操作"""
    global h
    masked_phone = phone[:3] + '****' + phone[-4:]
    current_time = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f')[:-3]
    print(f"[{current_time}] [兑换准备] {masked_phone} 准备兑换 {title}")

    try:
        # 时间设置
        now = get_network_time()
        if h is None:
            h = now.hour
            print(f"[{current_time}] [时间设置] 自动设置小时为: {h}")

        # 第一轮等待
        if h == 9:
            first_target_time = now.replace(hour=h, minute=59, second=56, microsecond=0)
        elif h == 13:
            first_target_time = now.replace(hour=h, minute=59, second=56, microsecond=0)
        else:
            first_target_time = now

        first_time_diff = (first_target_time - now).total_seconds()
        if 0 <= first_time_diff <= 300:
            print(f"[{current_time}] [等待开始] {masked_phone} 等待 {first_time_diff:.2f} 秒后开始兑换")
            await asyncio.sleep(first_time_diff)

        # 时间段判断
        morning_start = datetime.time(9, 30, 50)
        morning_end = datetime.time(10, 10, 5)
        afternoon_start = datetime.time(13, 30, 40)
        afternoon_end = datetime.time(14, 10, 5)
        current_time_obj = now.time()

        # 代理设置
        proxy = None
        # if (morning_start <= current_time_obj <= morning_end) or (afternoon_start <= current_time_obj <= afternoon_end):
        #     if DY_PROXY:
        #         try:
        #             proxy_ip = await get_proxy_from_pool()
        #             proxy = f"http://{proxy_ip}"
        #             print(f"[{current_time}] [代理使用] {masked_phone} 将使用代理IP: {proxy_ip}")
        #         except ValueError as e:
        #             print(f"[{current_time}] [代理警告] {masked_phone} {e}，将使用本地网络")
        #     else:
        #         print(f"[{current_time}] [网络设置] {masked_phone} 将使用本地网络")
        # else:
        #     print(f"[{current_time}] [网络设置] {masked_phone} 将使用本地网络")

        # 第二轮等待
        # if h == 9:
        #     second_target_time = now.replace(hour=h, minute=59, second=56, microsecond=803600)
        # elif h == 13:
        #     second_target_time = now.replace(hour=h, minute=59, second=56, microsecond=793600)
        # else:
        #     second_target_time = now

        # second_time_diff = (second_target_time - get_network_time()).total_seconds()
        # if 0 <= second_time_diff <= 300:
        #     print(f"[{current_time}] [等待开始] {masked_phone} 等待 {second_time_diff:.2f} 秒后执行兑换")
        #     await asyncio.sleep(second_time_diff)

        # 执行兑换请求
        url = "https://wapact.189.cn:9001/gateway/standExchange/detailNew/exchange"
        request_start_time = datetime.datetime.now()
        current_time_str = request_start_time.strftime('%Y-%m-%d %H:%M:%S.%f')[:-3]
        print(f"[{current_time_str}] [兑换请求] {masked_phone} 发送兑换 {title} 请求")

        try:
            async with s.post(url, json={"activityId": aid}, proxy=proxy) as r:
                request_end_time = datetime.datetime.now()
                response_time_str = request_end_time.strftime('%Y-%m-%d %H:%M:%S.%f')[:-3]
                response_time = (request_end_time - request_start_time).total_seconds()
                
                print(f"[{response_time_str}] [兑换响应] {masked_phone} 兑换请求响应，状态码: {r.status}, 耗时: {response_time:.6f}秒")
                
                response_text = await r.text()
                if len(response_text) > 500:
                    print(f"[{response_time_str}] [响应内容] {masked_phone} 响应内容过长，前500字符: {response_text[:500]}...")
                else:
                    print(f"[{response_time_str}] [响应内容] {masked_phone} 响应内容: {response_text}")

                if r.status == 412:
                    print(f"[{response_time_str}] [兑换错误] {masked_phone} 遇到412错误，终止本次兑换尝试")
                    return

                if r.status == 200:
                    try:
                        r_json = await r.json()
                        if r_json["code"] == 0:
                            if r_json["biz"] != {} and r_json["biz"]["resultCode"] in errcode:
                                result_msg = errcode[r_json["biz"]["resultCode"]]
                                print(f"[{response_time_str}] [兑换结果] {masked_phone} {title} {result_msg}")

                                # 处理成功或达上限的情况
                                if r_json["biz"]["resultCode"] in ["0", "412"]:
                                    if r_json["biz"]["resultCode"] == "0":
                                        msg = f"{masked_phone}: {title}兑换成功"
                                        send(uid, msg)
                                    
                                    # 更新兑换记录
                                    if title not in dhjl[yf]:
                                        dhjl[yf][title] = set()
                                    
                                    if phone not in dhjl[yf][title]:
                                        dhjl[yf][title].add(phone)
                                        # 保存记录
                                        temp_dhjl = {k: {m: list(n) for m, n in v.items()} for k, v in dhjl.items()}
                                        with open('电信金豆换话费.log', 'w') as f:
                                            json.dump(temp_dhjl, f, ensure_ascii=False)
                                        print(f"[{response_time_str}] [记录更新] {masked_phone} {title} 兑换记录已更新")
                        else:
                            print(f"[{response_time_str}] [兑换异常] {masked_phone} 兑换返回非0状态: {r_json}")
                    except json.JSONDecodeError:
                        print(f"[{response_time_str}] [解析错误] {masked_phone} 响应内容不是有效的JSON: {response_text}")
                else:
                    print(f"[{response_time_str}] [兑换失败] {masked_phone} 兑换请求返回非200状态: {r.status}")

        except (aiohttp.ClientError, asyncio.TimeoutError) as e:
            error_time = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f')[:-3]
            print(f"[{error_time}] [请求错误] {masked_phone} 兑换请求发生错误: {str(e)}")

    except Exception as e:
        error_time = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f')[:-3]
        print(f"[{error_time}] [兑换异常] {masked_phone} 兑换过程发生异常: {str(e)}")
        traceback.print_exc()


async def dh(phone, s, title, aid, wt, uid):
    """处理单个商品的多次兑换尝试"""
    global h
    masked_phone = phone[:3] + '****' + phone[-4:]
    current_time = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f')[:-3]
    print(f"[{current_time}] [批量兑换] {masked_phone} 开始 {title} 的批量兑换尝试")

    cs = 0
    tasks = []
    creat_start_time = datetime.datetime.now()
    
    # 创建兑换任务
    while cs < INNER_LOOP_COUNT:
        amount = title.split('元')[0]
        if (h == 9 and title in morning_exchanges) or (h == 13 and title in afternoon_exchanges):
            tasks.append(exchange(phone, s, title, aid, uid, amount))
            cs += 1
            await asyncio.sleep(0.05)  # 减少任务创建间隔，加快速度
        else:
            print(f"[{current_time}] [时间过滤] {masked_phone} {title} 不在当前兑换时段，跳过")
            break
    
    creat_end_time = datetime.datetime.now()
    create_duration = (creat_end_time - creat_start_time).total_seconds()
    print(f"[{current_time}] [任务创建] {masked_phone} 已创建 {cs} 个兑换任务，耗时: {create_duration:.6f}秒")

    # 等待目标时间
    while wt > get_network_time().timestamp():
        await asyncio.sleep(0.01)  # 减少等待间隔，提高精度
    
    # 执行所有兑换任务
    await asyncio.gather(*tasks)
    finish_time = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f')[:-3]
    print(f"[{finish_time}] [批量完成] {masked_phone} {title} 的 {cs} 次兑换尝试已完成")


def aes_ecb_encrypt(plaintext, key):
    """AES ECB模式加密"""
    try:
        key = key.encode('utf-8')
        if len(key) not in [16, 24, 32]:
            raise ValueError(f"密钥长度必须为16/24/32字节，当前为{len(key)}字节")

        padded_data = pad(plaintext.encode('utf-8'), AES.block_size)
        cipher = AES.new(key, AES.MODE_ECB)
        ciphertext = cipher.encrypt(padded_data)
        return base64.b64encode(ciphertext).decode('utf-8')
    except Exception as e:
        current_time = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f')[:-3]
        print(f"[{current_time}] [加密错误] AES加密失败: {str(e)}")
        raise


async def ks(phone, ticket, uid):
    """兑换主流程"""
    global h, wt
    masked_phone = phone[:3] + '****' + phone[-4:]
    current_time = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f')[:-3]
    print(f"[{current_time}] [流程开始] {masked_phone} 开始执行兑换主流程")

    headers = {
        "User-Agent": "Mozilla/5.0 (Linux; Android 13; 22081212C Build/TKQ1.220829.002) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.5112.97 Mobile Safari/537.36",
        "Referer": "https://wapact.189.cn:9001/JinDouMall/JinDouMall_independentDetails.html"
    }

    timeout = aiohttp.ClientTimeout(total=20)
    async with aiohttp.ClientSession(
        connector=aiohttp.TCPConnector(ssl=ssl_context), 
        headers=headers, 
        timeout=timeout
    ) as s:
        try:
            # 准备登录数据
            login_data = {
                "ticket": ticket,
                "backUrl": "https%3A%2F%2Fwapact.189.cn%3A9001",
                "platformCode": "P201010301",
                "loginType": 2
            }
            encrypted_data = aes_ecb_encrypt(json.dumps(login_data), 'telecom_wap_2018')
            
            # 登录重试机制
            max_retries = 3
            retries = 0
            login = None
            while retries < max_retries:
                try:
                    login_start_time = time_module.time()
                    login_response = await s.post(
                        'https://wapact.189.cn:9001/unified/user/login',
                        data=encrypted_data,
                        headers={
                            "Content-Type": "application/json;charset=UTF-8",
                            "Accept": "application/json, text/javascript, */*; q=0.01"
                        }
                    )
                    login_end_time = time_module.time()
                    print(f"[{current_time}] [登录请求] {masked_phone} 登录请求完成，耗时: {login_end_time - login_start_time:.3f}秒，状态码: {login_response.status}")

                    if login_response.status == 200:
                        login = await login_response.json()
                        break
                    elif login_response.status == 412:
                        print(f"[{current_time}] [登录重试] {masked_phone} 登录返回412，准备重试")
                        retries += 1
                        await asyncio.sleep(2 **retries)
                    else:
                        print(f"[{current_time}] [登录失败] {masked_phone} 登录状态码异常: {login_response.status}")
                        print(f"[{current_time}] [响应内容] {masked_phone} 登录响应: {await login_response.text()}")
                        retries += 1
                        await asyncio.sleep(2** retries)

                except (aiohttp.ClientError, asyncio.TimeoutError) as e:
                    retries += 1
                    print(f"[{current_time}] [登录异常] {masked_phone} 登录尝试 {retries}/{max_retries} 失败: {str(e)}")
                    await asyncio.sleep(2 **retries)

            if not login or login.get('code') != 0:
                print(f"[{current_time}] [登录失败] {masked_phone} 登录失败，响应: {login}")
                return

            # 设置Authorization
            s.headers["Authorization"] = "Bearer " + login["biz"]["token"]
            print(f"[{current_time}] [登录成功] {masked_phone} 登录成功，已设置Authorization")

            # 查询金豆余额
            try:
                balance_start = time_module.time()
                r = await s.get('https://wapact.189.cn:9001/gateway/golden/api/queryInfo')
                balance_end = time_module.time()
                r_json = await r.json()
                
                if r_json.get("code") == 0 and "biz" in r_json and "amountTotal" in r_json["biz"]:
                    amountTotal = r_json["biz"]["amountTotal"]
                    print(f"[{current_time}] [金豆查询] {masked_phone} 金豆余额: {amountTotal}个，耗时: {balance_end - balance_start:.3f}秒")
                else:
                    print(f"[{current_time}] [金豆异常] {masked_phone} 金豆查询响应异常: {r_json}")
            except Exception as e:
                print(f"[{current_time}] [金豆错误] {masked_phone} 查询金豆余额失败: {str(e)}")

            # 获取商品列表
            try:
                goods_start = time_module.time()
                query_url = 'https://wapact.189.cn:9001/gateway/golden/goldGoods/getGoodsList?floorType=0&userType=1&page=1&order=3&tabOrder='
                query_response = await s.get(query_url)
                goods_end = time_module.time()
                query_json = await query_response.json()
                print(f"[{current_time}] [商品列表] {masked_phone} 获取商品列表完成，耗时: {goods_end - goods_start:.3f}秒")

                if "biz" in query_json and "ExchangeGoodslist" in query_json["biz"]:
                    for item in query_json["biz"]["ExchangeGoodslist"]:
                        if '话费' not in item.get("title", ""):
                            continue
                        # 匹配上午兑换商品
                        for morning_item in morning_exchanges:
                            if morning_item in item["title"]:
                                jp["9"][morning_item] = item["id"]
                        # 匹配下午兑换商品
                        for afternoon_item in afternoon_exchanges:
                            if afternoon_item in item["title"]:
                                jp["13"][afternoon_item] = item["id"]
                    
                else:
                    print(f"[{current_time}] [商品异常] {masked_phone} 商品列表格式异常: {query_json}")
            except Exception as e:
                print(f"[{current_time}] [商品错误] {masked_phone} 获取商品列表失败: {str(e)}")

            # 确定当前时段
            h = get_network_time().hour
            if 11 > h:
                h = 9
            else:
                h = 13

            if len(sys.argv) == 2:
                h = int(sys.argv[1])
            print(f"[{current_time}] [时段确定] {masked_phone} 当前兑换时段: {h}点")

            # 获取当前时段的商品
            d = jp[str(h)]
            wt = t(h) + kswt
            print(f"[{current_time}] [目标时间] {masked_phone} 兑换目标时间戳: {wt}")

            # 过滤已兑换的商品
            valid_products = []
            for di in sorted(d.keys(), key=lambda x: float(x.replace('元话费', '')), reverse=True):
                if phone not in dhjl[yf].get(di, set()):
                    valid_products.append(di)
            print(f"[{current_time}] [商品过滤] {masked_phone} 有效兑换商品: {valid_products}")

            # 外层循环控制
            for loop in range(OUTER_LOOP_COUNT):
                loop_time = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f')[:-3]
                print(f"[{loop_time}] [轮次开始] {masked_phone} 开始第 {loop + 1}/{OUTER_LOOP_COUNT} 轮兑换")
                
                # 检查是否需要退出
                if wt - time_module.time() > 30 * 60:
                    print(f"[{loop_time}] [超时退出] {masked_phone} 距离目标时间超过30分钟，退出兑换")
                    return

                # 创建本轮兑换任务
                tasks = []
                for di in valid_products:
                    tasks.append(dh(phone, s, di, d[di], wt, uid))
                
                print(f"[{loop_time}] [任务数量] {masked_phone} 第 {loop + 1} 轮共有 {len(tasks)} 个兑换任务")
                await asyncio.gather(*tasks)

                # 轮次间隔
                if loop < OUTER_LOOP_COUNT - 1:
                    await asyncio.sleep(0.005)  # 减少轮次间隔，加快速度
                    next_loop_time = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f')[:-3]
                    print(f"[{next_loop_time}] [轮次间隔] {masked_phone} 第 {loop + 1} 轮完成，准备下一轮")

            final_time = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f')[:-3]
            print(f"[{final_time}] [流程完成] {masked_phone} 所有 {OUTER_LOOP_COUNT} 轮兑换已完成")

        except Exception as e:
            error_time = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f')[:-3]
            print(f"[{error_time}] [流程错误] {masked_phone} 兑换主流程发生错误: {str(e)}")
            traceback.print_exc()
            return


async def main():
    """主函数"""
    global wt, rs, h
    current_time = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f')[:-3]
    print(f"[{current_time}] [程序启动] 电信金豆兑换话费程序开始运行")

    headers = {
        "User-Agent": "Mozilla/5.0 (Linux; Android 13; 22081212C Build/TKQ1.220829.002) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.5112.97 Mobile Safari/537.36",
        "Referer": "https://wapact.189.cn:9001/JinDouMall/JinDouMall_independentDetails.html"
    }

    timeout = aiohttp.ClientTimeout(total=20)
    rs = 0
    accounts = []
    
    # 加载账号
    china_telecom_account = os.getenv('chinaTelecomAccount')
    if china_telecom_account:
        accounts.extend(re.split(r'@|&', china_telecom_account))
        current_time = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f')[:-3]
        print(f"[{current_time}] [账号加载] 成功加载 {len(accounts)} 个账号")
    else:
        print(f"[{current_time}] [账号错误] 未检测到任何账号，请检查环境变量 chinaTelecomAccount")
        return

    # 分批处理账号
    batch_size = 20
    total_batches = (len(accounts) + batch_size - 1) // batch_size
    print(f"[{current_time}] [批量设置] 账号将分为 {total_batches} 批处理，每批 {batch_size} 个")

    for i in range(0, len(accounts), batch_size):
        batch_accounts = accounts[i:i + batch_size]
        batch_num = i // batch_size + 1
        current_time = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f')[:-3]
        print(f"[{current_time}] [批次开始] 开始处理第 {batch_num}/{total_batches} 批账号，共 {len(batch_accounts)} 个")

        tasks = []
        for account in batch_accounts:
            try:
                account_info = account.split('#')
                if len(account_info) < 2:
                    current_time = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f')[:-3]
                    print(f"[{current_time}] [账号错误] 账号格式错误: {account}，正确格式应为 手机号#密码#uid")
                    continue

                phone = account_info[0]
                password = account_info[1]
                uid = account_info[-1] if len(account_info) > 2 else ""
                masked_phone = phone[:3] + '****' + phone[-4:]
                ticket = False

                # 尝试使用缓存登录
                if phone in load_token:
                    current_time = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f')[:-3]
                    print(f"[{current_time}] [登录方式] {masked_phone} 尝试使用缓存登录")
                    ticket = get_ticket(phone, load_token[phone]['userId'], load_token[phone]['token'])

                # 缓存登录失败则使用密码登录
                if not ticket:
                    current_time = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f')[:-3]
                    print(f"[{current_time}] [登录方式] {masked_phone} 缓存登录失败，尝试密码登录")
                    ticket = userLoginNormal(phone, password)

                # 登录成功则添加到任务列表
                if ticket:
                    current_time = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f')[:-3]
                    print(f"[{current_time}] [任务添加] {masked_phone} 登录成功，添加到兑换任务列表")
                    tasks.append(ks(phone, ticket, uid))
                else:
                    current_time = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f')[:-3]
                    print(f"[{current_time}] [登录失败] {masked_phone} 登录失败，跳过该账号")
            except Exception as e:
                current_time = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f')[:-3]
                print(f"[{current_time}] [账号处理错误] 处理账号 {account} 时出错: {str(e)}")

        # 等待目标时间
        while wt > datetime.datetime.now().timestamp():
            await asyncio.sleep(0.01)  # 提高等待精度

        # 执行当前批次的所有任务
        if tasks:
            current_time = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f')[:-3]
            print(f"[{current_time}] [任务执行] 开始执行第 {batch_num} 批的 {len(tasks)} 个兑换任务")
            await asyncio.gather(*tasks)
        else:
            current_time = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f')[:-3]
            print(f"[{current_time}] [任务为空] 第 {batch_num} 批没有可执行的兑换任务")

        # 批次间隔
        batch_end_time = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f')[:-3]
        print(f"[{batch_end_time}] [批次完成] 第 {batch_num}/{total_batches} 批账号处理完成")
        if batch_num < total_batches:
            await asyncio.sleep(1)  # 批次间隔


START_LOG = rf'''
+--------------------------------------------------------------------+
|  🌸 欢迎使用 金豆兑换话费 ✨                                                              
+--------------------------------------------------------------------+
'''

if __name__ == "__main__":
    print(START_LOG)
    current_time = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f')[:-3]
    print(f"[{current_time}] [系统信息] 程序会提前【{kswt} 秒】准备")
    
    if len(sys.argv) > 1:
        h = int(sys.argv[1])
        print(f"[{current_time}] [参数设置] 通过命令行参数指定小时: {h}")
    else:
        h = None
        print(f"[{current_time}] [参数设置] 未指定小时，将自动判断")
    
    asyncio.run(main())

# 生成用户视角的兑换记录
current_month = get_network_time().strftime("%Y%m")
try:
    with open('电信金豆换话费.log', 'r') as fr:
        dhjl = json.load(fr)
    current_time = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f')[:-3]
    print(f"[{current_time}] [记录处理] 加载兑换记录用于生成用户视角日志")
except FileNotFoundError:
    current_time = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f')[:-3]
    print(f"[{current_time}] [记录处理] 未找到兑换记录文件")
    dhjl = {}

# 转换记录格式
dhjl2 = {}
if current_month in dhjl:
    records = dhjl[current_month]
    for fee, phones in records.items():
        if isinstance(phones, list):
            phone_list = phones
        else:
            phone_list = phones.strip('#').split('#')
        
        for phone in phone_list:
            masked_phone = phone[:3] + '****' + phone[-4:]
            if masked_phone not in dhjl2:
                dhjl2[masked_phone] = {}
            if current_month not in dhjl2[masked_phone]:
                dhjl2[masked_phone][current_month] = []
            dhjl2[masked_phone][current_month].append(fee)

# 保存转换后的记录
with open('电信金豆换话费2.log', 'w') as fw:
    json.dump(dhjl2, fw, ensure_ascii=False, indent=4)
current_time = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f')[:-3]
print(f"[{current_time}] [记录处理] 用户视角兑换记录已保存到 电信金豆换话费2.log")

# 推送时间判断
current_time = get_network_time()
start_time_1 = current_time.replace(hour=10, minute=0, second=30)
end_time_1 = current_time.replace(hour=10, minute=10, second=0)
start_time_2 = current_time.replace(hour=14, minute=0, second=30)
end_time_2 = current_time.replace(hour=14, minute=10, second=0)

in_time_window = (start_time_1 <= current_time < end_time_1) or (start_time_2 <= current_time < end_time_2)
final_time = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f')[:-3]
if in_time_window:
    print(f"[{final_time}] [程序结束] 任务执行完成，当前在推送时间窗口内")
else:
    print(f"[{final_time}] [程序结束] 任务执行完成，当前不在推送时间窗口内")



